/*
 * Copyright (c) 2024-2024 Huawei Technologies Co., Ltd.
 * openFuyao is licensed under Mulan PSL v2.
 * You can use this software according to the terms and conditions of the Mulan PSL v2.
 * You may obtain a copy of Mulan PSL v2 at:
 *          http://license.coscl.org.cn/MulanPSL2
 * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
 * EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
 * MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
 * See the Mulan PSL v2 for more details.
 */

package deviceplugin

import (
	"fmt"
	"io/fs"
	"io/ioutil"
	"net"
	"os"
	"path"
	"reflect"
	"testing"
	"time"

	"github.com/agiledragon/gomonkey/v2"
	"github.com/stretchr/testify/assert"
	"google.golang.org/grpc"
	"k8s.io/kubelet/pkg/apis/deviceplugin/v1beta1"

	"openfuyao.com/kae-device-plugin/pkg/zlog"
)

const (
	kubcletEndpoint = "kubelet.sock"
	waitServerReady = 2 * time.Second
)

// TestNewKaeManager 测试 NewKAEManager
func TestNewKaeManager(t *testing.T) {
	testManager := NewKAEManager()
	assert.NotNil(t, testManager)
}

// TestDiscoverDevices 测试 DiscoverDevices
func TestDiscoverDevices(t *testing.T) {
	manager := &KAEManager{
		grpcServer: &grpc.Server{},
		devices:    make(map[string]v1beta1.Device),
	}
	patches := gomonkey.NewPatches()
	defer patches.Reset()

	patches.ApplyFunc(
		ioutil.ReadDir, func(dirname string) ([]fs.FileInfo, error) {
			return nil, fmt.Errorf("stat failed")
		},
	)
	assert.NotNil(t, manager.discoverDevices())

	patches.Reset()
	patches.ApplyFunc(
		ioutil.ReadDir, func(dirname string) ([]fs.FileInfo, error) {
			return nil, nil
		},
	)
	assert.NotNil(t, manager.discoverDevices())

	patches.Reset()
	os.Create("/dev/hisi_sectest")
	defer os.Remove("/dev/hisi_sectest")
	files, _ := ioutil.ReadDir(devDirectory)
	patches.ApplyFunc(
		ioutil.ReadDir, func(dirname string) ([]fs.FileInfo, error) {
			return files, nil
		},
	)
	assert.NotNil(t, manager.discoverDevices())
}

// TestStart 测试 Start
func TestStart(t *testing.T) {
	manager := &KAEManager{
		grpcServer: &grpc.Server{},
		devices:    make(map[string]v1beta1.Device),
	}
	patches := gomonkey.NewPatches()
	defer patches.Reset()

	patches.ApplyFunc(
		os.Stat, func(name string) (os.FileInfo, error) {
			return nil, fmt.Errorf("stat failed")
		},
	)
	assert.NotNil(t, manager.Start())

	patches.Reset()
	outputs := []gomonkey.OutputCell{
		{Values: gomonkey.Params{nil, nil}},
		{Values: gomonkey.Params{nil, fmt.Errorf("stat file failed")}},
	}
	patches.ApplyFuncSeq(os.Stat, outputs)
	assert.NotNil(t, manager.Start())

	patches.Reset()
	outputs = []gomonkey.OutputCell{
		{Values: gomonkey.Params{nil, nil}},
		{Values: gomonkey.Params{nil, nil}},
		{Values: gomonkey.Params{nil, fmt.Errorf("stat file failed")}},
	}
	patches.ApplyFuncSeq(os.Stat, outputs)
	assert.NotNil(t, manager.Start())

	patches.Reset()
	outputs = []gomonkey.OutputCell{
		{Values: gomonkey.Params{nil, nil}},
		{Values: gomonkey.Params{nil, nil}},
		{Values: gomonkey.Params{nil, nil}},
	}
	patches.ApplyFuncSeq(os.Stat, outputs)
	patches.ApplyFunc(
		(*KAEManager).discoverDevices, func(_ *KAEManager) error {
			return nil
		},
	)
	assert.Nil(t, manager.Start())
}

// TestServe 测试 serve
func TestServe(t *testing.T) {
	patches := gomonkey.NewPatches()
	defer patches.Reset()

	manger := NewKAEManager()
	pluginEndpointPath := path.Join("/dev", pluginEndpointPrefix)
	manger.grpcServer = grpc.NewServer()
	plugin := &pluginService{kaeManager: manger}
	plugin.RegisterService()
	lis, err := net.Listen("unix", pluginEndpointPath)
	dClient, err := NewDeviceClient(pluginEndpointPath)
	if err != nil {
		zlog.Error("create dClient failed, err: %v", err)
		return
	}

	patches.Reset()
	count := 0
	patches.ApplyFunc(
		net.Listen, func(network string, address string) (net.Listener, error) {
			count++
			return lis, nil
		},
	)

	patches.ApplyMethod(
		reflect.TypeOf(&grpc.Server{}), "Serve", func(s *grpc.Server, lis net.Listener) error {
			return fmt.Errorf("serve failed")
		},
	)
	patches.ApplyFunc(
		NewDeviceClient, func(sock string) (*DeviceClient, error) {
			return dClient, nil
		},
	)
	patches.ApplyFunc(
		devicePluginMonitor, func(manager *KAEManager, pluginEndpointPath string) {
			return
		},
	)
	patches.ApplyMethod(
		reflect.TypeOf(dClient), "CheckListAndWatch", func(dClient *DeviceClient) {
			return
		},
	)

	go func() {
		manger.stop = make(chan bool, 1)
		time.Sleep(time.Second)
		manger.stop <- false
	}()
	manger.Serve(kubcletEndpoint)
	assert.NotNil(t, manger.Serve)
}

// TestRegisterWithKubelet 测试 RegisterWithKubelet
func TestRegisterWithKubelet(t *testing.T) {
	manager := &KAEManager{
		grpcServer: &grpc.Server{},
		devices:    make(map[string]v1beta1.Device),
	}

	err := registerWithKubelet(manager, "kubesec.sock", resourceName)
	assert.NotNil(t, err)

	os.Create("/dev/hisi_sectest")
	defer os.Remove("/dev/hisi_sectest")
	os.Create("/dev/kubelet.sock")
	defer os.Remove("/dev/kubelet.sock")
	patches := gomonkey.NewPatches()
	defer patches.Reset()

	patches.ApplyMethod(
		reflect.TypeOf(&grpc.Server{}), "GetServiceInfo", func(_ *grpc.Server) map[string]grpc.ServiceInfo {
			a := make(map[string]grpc.ServiceInfo, 1)
			var b grpc.ServiceInfo
			a["first"] = b
			return a
		},
	)
	patches.ApplyFunc(
		RegisterWithV1Beta1Kubelet, func(_ string, _ string) error {
			return nil
		},
	)
	err = registerWithKubelet(manager, "kubesec.sock", resourceName)
	assert.NotNil(t, err)

	patches.Reset()
	patches.ApplyMethod(
		reflect.TypeOf(&grpc.Server{}), "GetServiceInfo", func(_ *grpc.Server) map[string]grpc.ServiceInfo {
			a := make(map[string]grpc.ServiceInfo, 1)
			var b grpc.ServiceInfo
			a["first"] = b
			return a
		},
	)
	patches.ApplyFunc(
		RegisterWithV1Beta1Kubelet, func(_ string, _ string) error {
			return fmt.Errorf("break")
		},
	)
	patches.ApplyMethod(
		reflect.TypeOf(&grpc.Server{}), "Stop", func(x *grpc.Server) {
			return
		},
	)
	err = registerWithKubelet(manager, "/dev/", resourceName)
	assert.NotNil(t, err)
}

// TestRemoveKAESock 测试RemoveKAESock
func TestRemoveKAESock(t *testing.T) {
	patches := gomonkey.NewPatches()
	defer patches.Reset()

	patches.ApplyFunc(
		ioutil.ReadDir, func(_ string) ([]fs.FileInfo, error) {
			var a []fs.FileInfo
			return a, fmt.Errorf("break")
		},
	)
	RemoveKAESock()
	assert.NotNil(t, RemoveKAESock)
}

// TestCycleRegisterWithKubelet 测试 CycleRegisterWithKubelet
func TestCycleRegisterWithKubelet(t *testing.T) {
	manager := &KAEManager{
		grpcServer: &grpc.Server{},
		devices:    make(map[string]v1beta1.Device),
	}

	os.Create("/dev/hisi_sectest")
	defer os.Remove("/dev/hisi_sectest")
	os.Create("/dev/kubelet.sock")
	defer os.Remove("/dev/kubelet.sock")
	patches := gomonkey.NewPatches()
	defer patches.Reset()

	patches.ApplyFunc(
		registerWithKubelet, func(kaeManager *KAEManager, pluginEndpoint,
			resourceName string) error {
			return nil
		},
	)
	cycleRegisterWithKubelet(manager, "/dev/", resourceName)
	assert.NotNil(t, cycleRegisterWithKubelet)
}
